//双token无感刷新，与单点登录  样例
// 本次我我们模拟的是接口的返回token令牌 不是在响应结果里面返回  而是在响应头里面返回
// 比如端token    authorization   和 长token   refreshToken  都在相应头里
import axios from "axios";
import throttle from "lodash/throttle";
import { message } from "antd";
import getCookie from "./cookie.js";
import { getRefreshToken, isGetRefreshToken } from "@/utils/refreshToken.js";

//手动中断请求
const requestControllerMap = new Map();
const getRequestKey = (config) => {
  const { method, url } = config;
  return `${method}-${url}`;
};
//中断前一个相同的请求 也可以用作手动中断某个具体的请求
export const cancelPendingRequest = (key) => {
  if (requestControllerMap.has(key)) {
    const controller = requestControllerMap.get(key);
    controller.abort();
    requestControllerMap.delete(key);
  }
};
//终止所有请求
export const cancelAllRequest = () => {
  requestControllerMap.forEach((controller) => controller.abort());
  requestControllerMap.clear();
};
const instance = axios.create({
  baseURL: "",
  timeout: 1000 * 5,
  headers: {
    "X-Requested-With": "XMLHttpRequest",
    "Content-Type": "application/json",
  },
  withCredentials: false, //表示跨域请求时是否需要使用凭证
});
instance.defaults.retry = 2;
instance.defaults.retryDelay = 1000;
instance.defaults.headers.post["Content-Type"] =
  "application/json;charset=UTF-8";
let refreshNum = 3;
const retryConnect = (obj) => {
  let config = obj.config; //获取错误信息
  if (!config || !config.retry) return Promise.reject(obj);
  config.__retryCount = config.__retryCount || 0;
  if (config.__retryCount >= config.retry) {
    throttle(() => {
      message.error("网络连接失败...");
    }, 1000);
    return Promise.reject(obj);
  }
  //重试次数记录  请求次数记录在请求中  访问次数记录在路由中
  config.__retryCount += 1;
  let retryPromise = new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, config.retryDelay || 1);
  });
  return retryPromise.then(() => {
    return instance(config);
  });
};
instance.interceptors.request.use(
  (config) => {
    //都在响应头里所以就不在这里单独指定了  见响应拦截器
    // let token = getCookie.get("token");
    // token && (config.headers.Authorization = token);

    //跳过刷新token的请求
    if (!isGetRefreshToken(config)) {
      const key = getRequestKey(config);
      cancelPendingRequest(key);
      const controller = new AbortController();
      config.signal = controller.signal;
      requestControllerMap.set(key, controller);
      //请求正常响应后从 map 中清理
      config.__requestKey = key;
    }
    if (config.data instanceof FormData) {
      //处理文件格式的请求  文件上传
      config.headers["Content-Type"] = "multipart/form-data;charset=UTF-8";
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  },
);
instance.interceptors.response.use(
  async (response) => {
    //清理掉正常响应的请求存储
    if (response.config.__requestKey) {
      requestControllerMap.delete(response.config.__requestKey);
    }

    //在这里处理长短双token的逻辑  可以和侯丹约定 只有信息验证的时候才带此响应头也可以
    if (response.data && response.data.data.authorization) {
      //获取响应头结果中的的短token  当然也可以放在响应头 和后端沟通即可
      const token = response.data.data.authorization;
      //本地存储
      getCookie.set("token", token);
      //设定下一次请求带上token
      instance.defaults.headers.Authorization = token;
    }
    if (response.data && response.data.data.refreshToken) {
      //获取响应结果中中的长token
      const refreshToken = response.data.data.refreshToken;
      //本地存储
      getCookie.set("refreshToken", refreshToken);
    }
    //登录过期无感刷新
    // 就是做两件事 1.就是用长token去请求短token  2.用短token再重新请求 但是要考虑未登录情况,排除掉 长token请求情况
    //如果token 过期 401  就要无感刷新
    if (
      response.data.code === 401 &&
      !isGetRefreshToken(response.config) &&
      refreshNum > 0
    ) {
      refreshNum--;
      const res = await getRefreshToken();
      if (res && res.code === 200) {
        if (response.config.__requestKey) {
          requestControllerMap.delete(response.config.__requestKey);
        }
        response.config.headers.Authorization = getCookie.get("token");
        const r = await instance.request(response.config);
        return r;
      } else {
        //长token也过期了 则跳转登录页
        message.error("登录状态已过期，请重新登录");
        window.location.href = window.location.origin + "/login";
      }
    }
    return Promise.resolve(response.data);
  },
  (error) => {
    return retryConnect(error);
  },
);
const downFile = (url, params) => {
  // get 是 params   post 是data
  // content-disposition = attachment; filename=report.xlsx  这类格式的  不走重试
  return axios
    .request({
      responseType: "blob",
      ...params,
      url,
    })
    .then((response) => {
      // 从响应头获取文件名
      const disposition = response.headers["content-disposition"];
      let filename = "file";
      if (disposition) {
        // 处理UTF-8编码的文件名
        const utf8Filename = disposition.match(/filename\*=UTF-8''([^;]+)/i);
        if (utf8Filename) {
          filename = decodeURIComponent(utf8Filename[1]);
        } else {
          const match = disposition.match(/filename="?([^;"]+)"?/i);
          if (match) {
            filename = match[1];
          }
        }
      }
      if (response.data instanceof Blob) {
        return {
          fileName: filename,
          fileData: response.data,
        };
      } else {
        message.error("下载失败");
      }
    });
};

instance.all = axios.all;
instance.spread = axios.spread;
instance.downFile = downFile;
export default instance;
